7. Accesso CRUD a risorse¶
In un’architettura orientata alle risorse le API vengono utilizzate, più che per eseguire compiti complessi, per eseguire operazioni di tipo CRUD - Create, Read, Update, Delete - su risorse del dominio di interesse. Ad esempio, una prenotazione è una risorsa che può essere creata (quando viene fissata), letta, modificata ed eliminata.
In questo scenario si assume che le API siano utilizzate per la gestione da parte del fruitore delle risorse messe a disposizione dall’erogatore. L’insieme di operazioni CRUD offerte dall’erogatore dipende dalla natura della risorse e dalla relazione costruita con i fruitori: sono possibili relazioni in cui l’erogatore rende disponibile ai fruitori la sola operazione di lettura (Read).
Figura 5 - Interazione CRUD
Il sequence diagram è, come si nota in figura, equivalente a quello di una RPC bloccante. In questo caso la richiesta DEVE contenere:
- Una operazione da effettuare sulla risorsa da scegliere tra Create, Read, Update e Delete;
- Un percorso che indica un identificativo di risorsa (es. una specifica prenotazione) oppure di una collezione di risorse (es. un insieme di prenotazioni);
- Nel caso di una operazione di Create o Update, un pacchetto dati indicante come inizializzare o modificare (anche parzialmente) la risorsa in questione.
Per quanto sia possibile sviluppare funzionalità CRUD anche sviluppando una interfaccia di servizio SOAP, il ModI prevede lo sviluppo di una interfaccia REST.
7.1. [CRUD_REST] CRUD REST¶
7.1.1. Regole di processamento¶
L’approccio RESTful trova la sua applicazione naturale in operazioni CRUD - Create, Read, Update, Delete su risorse ed a tal fine sfrutta i metodi standard dell’HTTP per indicare tali operazioni. In particolare la seguente mappatura viene utilizzata:
CRUD | Metodo HTTP | Esito (stato HTTP) Se applicato a intera collezione (es. /clienti ) |
Esito (stato HTTP) Se applicato a risorsa specifica (es. /clienti/{id} ) |
---|---|---|---|
Create | POST | HTTP status 201 Created, l’header «Location» nella risposta può contenere un link a /clienti/{id} che indica l’ID creato. | 404 (Not Found), 409 (Conflict) se la risorsa è già esistente. |
Read | GET | 200 (OK), lista dei clienti. Implementare meccanismi di paginazione, ordinamento e filtraggio per navigare grandi liste. | 200 (OK), 404 (Not Found), se l’ID non è stato trovato o è non valido. |
Update: Replace/ Create | PUT | 405 (Method Not Allowed), a meno che non si voglia sostituire ogni risorsa nella collezione. | 200 (OK) o 204 (No Content). 404 (Not Found), se l’ID non è stato trovato o è non valido. 201 (Created), nel caso di UPSERT. |
Update: Modify | PATCH | 405 (Method Not Allowed), a meno che non si voglia applicare una modifica ad ogni risorsa nella collezione. | 200 (OK) o 204 (No Content). 404 (Not Found), se l’ID non è stato trovato o è non valido. |
Delete | DELETE | 405 (Method Not Allowed), a meno che non si voglia permettere di eliminare l’intera collezione. | 200 (OK). 404 (Not Found), se l’ID non è stato trovato o è non valido. |
Si noti l’uso distinto di HTTP method PUT e HTTP method PATCH per la sostituzione e l’applicazione di modifiche ad una risorsa rispettivamente. È possibile utilizzare anche altri HTTP method a patto che si rispettino i dettami dell’approccio RESTful.
In alcuni casi l” HTTP method PUT può essere utilizzato con funzionalità di UPSERT (Update o Insert). In particolare, questo è necessario nei casi in cui sia il fruitore a definire gli identificativi del sistema erogatore.
Per usare il HTTP method PATCH bisogna usare alcuni accorgimenti, perché questo metodo non è definito nelle nuove specifiche di HTTP/1.1 del 2014 ma nel precedente RFC 5789.
NON SI DOVREBBE associare un significato di patch a dei media-type che
non lo prevedano (eg. application/json
o application/xml
) ma utilizzare
dei media-type adeguati [1].
È possibile ad esempio usare application/merge-patch+json
definito in
RFC 7396 facendo attenzione:
- che HTTP method PATCH rifiuti richieste con media-type non adeguato con HTTP status 415 Unsupported Media Type;
- che il media-type di patching sia compatibile con gli schemi utilizzati;
- di verificare le considerazioni di sicurezza presenti in RFC 7396#section-5 e RFC 5789#section-5.
7.1.2. Esempio¶
Per illustrare l’approccio RESTful al CRUD, faremo l’esempio di un API per gestire le prenotazioni di un appuntamento presso un ufficio municipale. L’erogatore verifica la compatibilità con la disponibilità nello specifico orario ed accetta o nega la creazione o l’eventuale variazione. Come da specifica seguente i metodi implementati sono HTTP method POST (creazione), HTTP method DELETE (eliminazione), HTTP method PATCH (modifica) e HTTP method GET (lettura).
Specifica Servizio Server
https://api.ente.example/rest/appuntamenti/v1/openapi.yaml
openapi: 3.0.1
info:
title: RESTCRUD
version: "1.0"
description: |-
Questo file descrive semplicemente i metodi di un'API
e non indica tutte le informazioni di metadatazione che
normalmente vanno inserite.
license:
name: Apache 2.0 License
url: http://www.apache.org/licenses/LICENSE-2.0.html
paths:
/municipio/{id_municipio}/ufficio/{id_ufficio}/prenotazioni:
get:
description: Mostra prenotazioni
operationId: listReservations
parameters:
- $ref: '#/components/parameters/limit'
- $ref: '#/components/parameters/cursor'
- $ref: '#/components/parameters/path_id_municipio'
- $ref: '#/components/parameters/path_id_ufficio'
responses:
'200':
description: Una lista di prenotazioni.
content:
application/json:
schema:
properties:
prenotazioni:
type: array
items:
$ref: '#/components/schemas/Prenotazione'
count:
type: integer
format: int32
next:
type: string
'400':
$ref: '#/components/responses/400BadRequest'
'404':
$ref: '#/components/responses/404NotFound'
default:
$ref: '#/components/responses/default'
post:
description: Aggiungi una prenotazione
operationId: AddReservation_1
parameters:
- $ref: '#/components/parameters/path_id_municipio'
- $ref: '#/components/parameters/path_id_ufficio'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Prenotazione'
responses:
'201':
description: Prenotazione Creata.
headers:
Location:
description: ID della prenotazione creata
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/Prenotazione'
'400':
$ref: '#/components/responses/400BadRequest'
'404':
$ref: '#/components/responses/404NotFound'
default:
$ref: '#/components/responses/default'
/municipio/{id_municipio}/ufficio/{id_ufficio}/prenotazioni/{id_prenotazione}:
get:
description: LeggiPrenotazione
operationId: GetReservation_1
parameters:
- $ref: '#/components/parameters/path_id_municipio'
- $ref: '#/components/parameters/path_id_ufficio'
- name: id_prenotazione
in: path
required: true
schema:
type: integer
format: int32
responses:
'200':
description: Prenotazione estratta correttamente
content:
application/json:
schema:
$ref: '#/components/schemas/Prenotazione'
'400':
$ref: '#/components/responses/400BadRequest'
'404':
$ref: '#/components/responses/404NotFound'
default:
$ref: '#/components/responses/default'
delete:
description: EliminaPrenotazione
operationId: DeleteReservation
parameters:
- $ref: '#/components/parameters/path_id_municipio'
- $ref: '#/components/parameters/path_id_ufficio'
- name: id_prenotazione
in: path
required: true
schema:
type: integer
format: int32
responses:
'200':
description: Prenotazione eliminata correttamente
'404':
$ref: '#/components/responses/404NotFound'
default:
$ref: '#/components/responses/default'
patch:
description: ModificaPrenotazione
operationId: PatchReservation
parameters:
- $ref: '#/components/parameters/path_id_municipio'
- $ref: '#/components/parameters/path_id_ufficio'
- name: id_prenotazione
in: path
required: true
schema:
type: integer
format: int32
requestBody:
content:
application/merge-patch+json:
schema:
$ref: '#/components/schemas/PatchPrenotazione'
responses:
'200':
description: Prenotazione modificata correttamente
content:
application/json:
schema:
$ref: '#/components/schemas/Prenotazione'
'400':
$ref: '#/components/responses/400BadRequest'
'404':
$ref: '#/components/responses/404NotFound'
default:
$ref: '#/components/responses/default'
components:
parameters:
path_id_municipio:
name: id_municipio
in: path
required: true
schema:
type: integer
format: int32
path_id_ufficio:
name: id_ufficio
in: path
required: true
schema:
type: integer
format: int32
limit:
description: How many items to return at one time (max 100)
in: query
name: limit
schema:
format: int32
type: integer
cursor:
description: An opaque identifier that points to the next item in the collection.
example: 01BX9NSMKVXXS5PSP2FATZM123
in: query
name: cursor
schema:
type: string
responses:
400BadRequest:
description: Richiesta non accoglibile
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorMessage'
404NotFound:
description: Identificativo non trovato
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorMessage'
default:
description: |-
Errore inatteso. Questo viene ritornato nel caso ci sia
un errore inatteso. Non vanno mai esposti i dati interni
del server.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorMessage'
schemas:
TaxCode:
description: Il codice fiscale.
example: RSSMRA75L01H501A
externalDocs:
url: https://w3id.org/italia/onto/CPV/taxCode
pattern: /^(?:(?:[B-DF-HJ-NP-TV-Z]|[AEIOU])[AEIOU][AEIOUX]|[B-DF-HJ-NP-TV-Z]{2}[A-Z]){2}[\dLMNP-V]{2}(?:[A-EHLMPR-T](?:[04LQ][1-9MNP-V]|[1256LMRS][\dLMNP-V])|[DHPS][37PT][0L]|[ACELMRT][37PT][01LM])(?:[A-MZ][1-9MNP-V][\dLMNP-V]{2}|[A-M][0L](?:[1-9MNP-V][\dLMNP-V]|[0L][1-9MNP-V]))[A-Z]$/i
type: string
Prenotazione:
type: object
properties:
nome:
type: string
cognome:
type: string
codice_fiscale:
$ref: '#/components/schemas/TaxCode'
dettagli:
$ref: '#/components/schemas/PatchPrenotazione'
PatchPrenotazione:
type: object
properties:
data:
type: string
format: date-time
motivazione:
type: string
ErrorMessage:
type: object
properties:
detail:
description: |
A human readable explanation specific to this occurrence of the
problem.
type: string
instance:
description: |
An absolute URI that identifies the specific occurrence of the problem.
It may or may not yield further information if dereferenced.
format: uri
type: string
status:
description: |
The HTTP status code generated by the origin server for this occurrence
of the problem.
exclusiveMaximum: true
format: int32
maximum: 600
minimum: 100
type: integer
title:
description: |
A short, summary of the problem type. Written in english and readable
for engineers (usually not suited for non technical stakeholders and
not localized); example: Service Unavailable
type: string
type:
default: about:blank
description: |
An absolute URI that identifies the problem type. When dereferenced,
it SHOULD provide human-readable documentation for the problem type
(e.g., using HTML).
format: uri
type: string
Di seguito un esempio di chiamata per creare una prenotazione.
- Request
POST /rest/appuntamenti/v1/municipio/{id_municipio}/ufficio/{id_ufficio}/prenotazioni HTTP/1.1
Host: api.ente.example
Content-Type: application/json
{
"nome_proprio": "Mario",
"cognome": "Rossi",
"codice_fiscale": "MRORSS77T05E472I",
"dettagli": {
"data": "2018-12-03T14:29:12.137Z",
"motivazione": "string"
}
}
- Response
HTTP/1.1 201 Created
Content-Type: application/json
Location: https://api.ente.example/rest/appuntamenti/v1/municipio/{id_municipio}/ufficio/{id_ufficio}/prenotazioni/12323254
{
"id": 12323254,
"nome_proprio": "Mario",
"cognome": "Rossi",
"codice_fiscale": "MRORSS77T05E472I",
"dettagli": {
"data": "2018-12-03T14:29:12.137Z",
"motivazione": "string"
}
}
Di seguito, un esempio in cui il fruitore richiede l’estrazione di una specifica prenotazione. Si noti l’utilizzo dell’URL restituito nell’ HTTP header Location al passo precedente.
- Request
GET /rest/appuntamenti/v1/municipio/{id_municipio}/ufficio/{id_ufficio}/prenotazioni/12323254 HTTP/1.1
Host: api.ente.example
- Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 12323254,
"nome_proprio": "Mario",
"cognome": "Rossi",
"codice_fiscale": "MRORSS77T05E472I",
"dettagli": {
"data": "2018-12-03T14:29:12.137Z",
"motivazione": "string"
}
}
Di seguito una richiesta di modifica dei dettagli di una prenotazione.
- Request
PATCH /rest/appuntamenti/v1/municipio/{id_municipio}/ufficio/{id_ufficio}/prenotazioni/12323254 HTTP/1.1
Host: api.ente.example
Content-Type: application/merge-patch+json
{
"dettagli": {
"data": "2018-12-03T14:29:12.137Z",
"motivazione": "nuova motivazione"
}
}
- Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"nome_proprio": "Mario",
"cognome": "Rossi",
"codice_fiscale": "MRORSS77T05E472I",
"dettagli": {
"data": "2018-12-03T14:29:12.137Z",
"motivazione": "nuova motivazione"
}
}
Di seguito una richiesta di modifica dei dettagli di una prenotazione con media-type application/json, che non avendo una semantica di patching definita, dev’essere rifiutato seguendo le indicazioni presenti in RFC 5789#section-2.2. La response ritorna il media-type suggerito dalla specifica tramite HTTP header Accept-Patch
- Request
PATCH /rest/appuntamenti/v1/municipio/{id_municipio}/ufficio/{id_ufficio}/prenotazioni/12323254 HTTP/1.1
Host: api.ente.example
Content-Type: application/json
{
"dettagli": {
"data": "2018-12-03T14:29:12.137Z",
"motivazione": "nuova motivazione"
}
}
- Response
HTTP/1.1 415 Unsupported Media Type
Accept-Patch: application/merge-patch+json
Di seguito un esempio di cancellazione di una specifica prenotazione.
- Request
DELETE /rest/appuntamenti/v1/municipio/{id_municipio}/ufficio/{id_ufficio}/prenotazioni/12323254 HTTP/1.1
Host: api.ente.example
- Response
HTTP/1.1 200 OK
[1] | Cf. https://www.rfc-editor.org/errata/eid3169 |